Hello World
The smallest useful Pebble contract: a script that locks a UTxO until the owner signs a transaction that includes a fixed greeting in the redeemer. It exercises the contract/state machinery, signature-checking, and a redeemer payload — everything the larger examples build on.
On-chain: hello_world.pebble
contract HelloWorld
{
state Locked {
owner: PubKeyHash
spend greet(message: bytes)
{
const { tx, state: { owner } } = context;
assert tx.signatories.includes(owner);
assert message == "Hello pebble".toBytes();
}
}
}
Why this shape
- One
stateconstructor, so the datum isLocked{ owner }— a single CBORConstr(0, [B owner]). - The redeemer is
(message: bytes)— the method's parameters become the redeemer at the call site. - The script aborts if the message doesn't match exactly. See Failures.
Off-chain: TypeScript with @harmoniclabs/buildooor
import {
Address, Credential, Hash28,
DataConstr, DataB,
TxBuilder, TxOut,
} from "@harmoniclabs/buildooor";
import { readFile } from "fs/promises";
import { provider } from "./provider";
const scriptCbor = await readFile("./hello_world.uplc");
const helloHash = new Hash28(/* blake2b_224(scriptCbor) */);
const helloAddr = new Address("mainnet", Credential.script(helloHash));
Lock
async function lock({ ownerPkh, lockValue, wallet, privateKey }) {
// Constr(0, [B ownerPkh]) — single-constructor sum for state `Locked`
const datum = new DataConstr(0, [ new DataB(ownerPkh.toBuffer()) ]);
const txBuilder = new TxBuilder(await provider.getProtocolParameters());
const tx = txBuilder.buildSync({
inputs: wallet.utxos.map((utxo) => ({ utxo })),
outputs: [
new TxOut({
address: helloAddr,
value: lockValue,
datum,
}),
],
changeAddress: wallet.address,
});
tx.signWith(privateKey);
return await provider.submitTx(tx);
}
Greet (unlock)
async function greet({ lockedUtxo, ownerWallet, ownerPrivateKey, scriptRefUtxo }) {
const txBuilder = new TxBuilder(await provider.getProtocolParameters());
const message = new TextEncoder().encode("Hello pebble");
const redeemer = new DataConstr(0, [ new DataB(message) ]); // greet(bytes)
const tx = txBuilder.buildSync({
inputs: [
{
utxo: lockedUtxo,
referenceScript: { refUtxo: scriptRefUtxo, redeemer },
},
...ownerWallet.utxos.map((utxo) => ({ utxo })),
],
outputs: [
new TxOut({
address: ownerWallet.address,
value: lockedUtxo.resolved.value,
}),
],
collaterals: [ ownerWallet.utxos[0] ],
changeAddress: ownerWallet.address,
requiredSigners: [ ownerWallet.pkh ], // satisfies tx.signatories.includes(owner)
});
tx.signWith(ownerPrivateKey);
return await provider.submitTx(tx);
}
Uses
contract,state,spendassert,tx.signatories.includes(...)string.toBytes,bytesequality via==- buildooor:
TxBuilder.buildSync,DataConstr,DataB,Credential.script
See also
- Validators 101 —
contextshape and script-purpose dispatch - State — the keyword used for the datum
- Simple Order Book DEX — same patterns at protocol scale